使用 CallerArgumentExpression 簡化參數檢核
TLDR
- 使用
[CallerArgumentExpression]屬性可以自動擷取傳入參數的變數名稱,無需再透過Expression處理。 - 相比於使用
Expression<Func<T>>的舊做法,此方法效能更好且程式碼更簡潔。 - 配合
[NotNull]或[DoesNotReturnIf]等屬性,能讓編譯器正確識別 Nullable reference type 的狀態,避免不必要的警告。 - 建議優先使用 .NET 6/7 內建的
ArgumentNullException.ThrowIfNull等靜態方法,若需自定義檢核邏輯,則參考[CallerArgumentExpression]實作。
傳統 Expression 檢核法的問題
什麼情況下會遇到這個問題:當開發者為了簡化參數檢核,使用 Expression 來避免同時傳入參數值與參數名稱時。
在舊有的實作中,為了簡化參數檢查,通常會撰寫如下的工具類:
csharp
public static class ExceptionUtils {
public static void ThrowIfNull<T>(Expression<Func<T?>> expression) {
_ = expression.Compile().Invoke()
?? throw new ArgumentNullException(GetMemberName(expression));
}
private static string GetMemberName<T>(Expression<Func<T>> expression) {
if (expression.Body is not MemberExpression expressionBody) {
throw new ArgumentException("Expression 表達式錯誤。", nameof(expression));
}
return expressionBody.Member.Name;
}
}此做法雖然達到了簡化呼叫的目的,但存在以下缺點:
- 效能開銷:
Expression的編譯與執行比直接存取變數慢。 - 編譯器支援度:無法在參數上標記
[NotNull],導致編譯器無法識別檢查後的變數狀態,進而產生 Nullable reference type 的警告。
使用 CallerArgumentExpression 優化檢核
什麼情況下會遇到這個問題:當需要自定義參數檢核邏輯,且希望同時獲得編譯器對 Nullable reference type 的支援與簡潔的語法時。
透過 C# 10 引入的 [CallerArgumentExpression] 屬性,我們可以自動取得參數的變數名稱,而無需使用 Expression。以下是優化後的實作方式:
csharp
public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) {
if (argument is null) {
throw new ArgumentNullException(paramName);
}
}驗證結果
測試程式碼如下:
csharp
string? str = "";
Console.Write("未傳入 paramName 時,");
TestCallerArgumentExpression(str);
Console.Write("有傳入 paramName 時,");
TestCallerArgumentExpression(str, "str2");
void TestCallerArgumentExpression(object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) {
Console.WriteLine("paramName:" + paramName);
}執行結果:
text
未傳入 paramName 時,paramName: str
有傳入 paramName 時,paramName: str2此方法不僅能自動補全變數名稱,還能透過 [NotNull] 標記,讓編譯器在呼叫該方法後,正確判斷變數是否為 null。
結論與建議
- 優先使用官方 API:.NET 6 與 .NET 7 已經內建了
ArgumentNullException.ThrowIfNull與ArgumentException.ThrowIfNullOrWhiteSpace等方法,應優先使用這些標準庫。 - 自定義檢核:若需實作特殊的檢核邏輯,請使用
[CallerArgumentExpression]取代Expression,以提升效能並獲得完整的編譯器檢查支援。
異動歷程
- 2024-10-13 初版文件建立。
